import { Quaternion, Vector3 } from "@babylonjs/core/Maths/math.vector";
import { Landscape } from "../landscape/landscape";
import { AbstractWeapon } from "./abstract-weapon";
import { Axis } from "@babylonjs/core/Maths/math.axis";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { DefaultStage } from "../../default-stage";
import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh";
import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder";
import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial";
import { Color3, Color4 } from "@babylonjs/core/Maths/math.color";
import { ParticleSystem } from "@babylonjs/core/Particles/particleSystem";
import { RawAssetsUrl } from "../../../raw-assets-info";
import { RenderOrder } from "babylon-lib/misc/render-order";
import * as Data from '../../../data/data';
import { AnimationFrame } from "babylon-lib/index";
import { DEGREE } from "babylon-lib/tool/calculation";
import { Random } from "babylon-lib/tool/random";
import { ExplodeImprint } from "../landscape/explode-imprint";
import { TargetManager } from "../target/target-manager";
import { AbstractTarget } from "../target/abstract-target";
import { SoundManager } from "../sound-manager";


class Shell extends AbstractWeapon {

    private static _tq0 = Quaternion.Identity();
    private static _tv0 = new Vector3();
    private static _tv1 = new Vector3();

    public static readonly period = 20;

    private _req = {};
    private _position = new Vector3();
    private _angle = 15 * DEGREE;
    private _initialSpeed = 900;
    private _speed = 900;
    private _direction = new Vector3();
    private _body!: AbstractMesh;
    private _explode!: ParticleSystem;
    private _damping = 0.9996;
    private _container!: TransformNode;
    private _dispersion = 20;
    private _smkReq = {};
    private _rldReq = {};
    private _sndReq = {};
    private _effectThreshold = [40000, 10000, 2500, 400];

    constructor(name: string, stage: DefaultStage) {
        super(name, stage);
    }

    protected checkDamage(location: Vector3): void {
        const targets = TargetManager.signedTargets;
        for (const target of targets) {
            target.hitPoint -= this.setDamage(location, target.entity.position);
        }
        TargetManager.updateTargets();
    }

    private setDamage(location: Vector3, target: Vector3): number {
        const dis = Vector3.DistanceSquared(location, target);
        if (dis > this._effectThreshold[0]) return 0;
        if (dis > this._effectThreshold[1]) return Random.float(0, 0.2);
        if (dis > this._effectThreshold[2]) return Random.float(0.1, 0.9);
        if (dis > this._effectThreshold[3]) return Random.float(0.5, 1.5);
        return 2;
    }

    public fire(offset: Vector3): void {
        if (!this.available) return;
        AnimationFrame.cancel(this._req);
        this.available = false;
        this._body.position.copyFrom(this._container.position);
        this._body.position.x += Random.float(-this._dispersion, this._dispersion);
        this._body.position.z += Random.float(-this._dispersion, this._dispersion);
        this._body.position.addInPlace(offset);
        this._body.isVisible = true;
        let t = 0;
        this._speed = this._initialSpeed;
        const pos = new Vector3();
        AnimationFrame.request(() => {
            t++;
            this._speed *= this._damping;
            pos.copyFrom(this._body.position);
            Shell.calculateTrajectory(pos, t, this._speed, this._angle, this._direction, this._body);
            if (this._body.position.y <= 0 && t > 10) {
                this._body.isVisible = false;
                pos.copyFrom(this._body.position);
                pos.y = 10;
                this.explode(pos);
                this.checkDamage(pos);
                return true;
            }
            const d = Vector3.DistanceSquared(this._body.absolutePosition, this.stage.mainCamera.position);
            if ((d < SoundManager.shellSoundThreshold) && (!SoundManager.shell.isPlaying)) {
                SoundManager.shell.play(SoundManager.shellSoundOffset);
            }
        }, 16, 200, this._req);
    }

    private explode(location: Vector3): void {
        AnimationFrame.cancel(this._smkReq);
        AnimationFrame.cancel(this._rldReq);
        AnimationFrame.cancel(this._sndReq);
        const emi = this._explode.emitter as AbstractMesh;
        emi.position.copyFrom(location);
        this._explode.start();
        ExplodeImprint.generate(location);
        AnimationFrame.throttleByTime(this._smkReq, 3000, () => {
            this._explode.stop();
        }, false);
        AnimationFrame.throttleByTime(this._rldReq, Shell.period * 1000, () => {
            this.available = true;
        }, false);

        const dis = Vector3.Distance(location, this.stage.mainCamera.position);
        const n = dis / 340 * 1000;
        AnimationFrame.throttleByTime(this._sndReq, n, () => {
            SoundManager.explode.play();
        }, false);
    }

    public setInitialData(): void {
        this._container.position.x = this._position.x + Data.coordinateX.value;
        this._container.position.z = this._position.z + Data.coordinateZ.value;
    }

    public setRangeData(convergence: number): void {
        this._container.position.x -= Data.offsetX.value * convergence;
        this._container.position.z -= Data.offsetZ.value * convergence;
    }

    public setPosition(position: Vector3): void {
        this._position.copyFrom(position);
        this._container.position.copyFrom(this._position);
        this._body.position.copyFrom(this._position);
    }

    public setDirection(direction: Vector3): void {
        this._direction.copyFrom(direction);
    }

    protected initializeComponents(): void {
        this._container = new TransformNode(this.name + '-container', this.stage.mainScene);
        this.generateBody();
        this.generateExplode();
    }

    private generateExplode(): void {
        const texture = this.stage.loader(this.stage.mainScene).getTextureTaskInfo(RawAssetsUrl.textureYanwu)?.texture!;
        const m = MeshBuilder.CreateBox(this.name + '-model', { size: 0.1 }, this.stage.mainScene);
        m.position.set(0, 0, 0);

        const c = 50;
        const life = Shell.period;
        const size = 10;
        const ps = new ParticleSystem(this.name + '-smoke', c, this.stage.mainScene);
        ps.particleTexture = texture;
        ps.minLifeTime = life * 0.5;
        ps.maxLifeTime = life;
        ps.minEmitPower = 0;
        ps.maxEmitPower = 4;
        ps.minAngularSpeed = -0.2;
        ps.maxAngularSpeed = 0.2;
        ps.minInitialRotation = 0;
        ps.maxInitialRotation = 6.24;
        ps.emitRate = c * 10;
        ps.createSphereEmitter(10, 0);
        ps.emitter = m;
        const gv = new Vector3().copyFrom(Landscape.wind);
        gv.y += 0.1;
        gv.scaleInPlace(10);
        ps.gravity = gv;

        ps.addColorGradient(0, new Color4(1, 1, 1, 0.8));
        ps.addColorGradient(0.001, new Color4(1, 1, 1, 0.3));
        ps.addColorGradient(1, new Color4(1, 1, 1, 0));

        ps.addSizeGradient(0, size, size);
        ps.addSizeGradient(0.001, size, size * 2);
        ps.addSizeGradient(0.1, size * 1.5, size * 3);
        ps.addSizeGradient(1, size * 5, size * 10);

        ps.addVelocityGradient(0, 0, 1);

        ps.renderingGroupId = RenderOrder.id(RenderOrder.baseName);
        ps.blendMode = ParticleSystem.BLENDMODE_ADD;
        this._explode = ps;
    }

    private generateBody(): void {
        const m = MeshBuilder.CreateBox(this.name + '-shell', { width: 1, height: 1, depth: 10 }, this.stage.mainScene);
        const mat = new StandardMaterial(this.name + '-shell-material', this.stage.mainScene);
        mat.disableLighting = true;
        mat.emissiveColor = Color3.FromHexString('#ffffff');
        m.material = mat;
        m.position.set(0, 0, 0);
        m.isVisible = false;
        this._body = m;
    }

    public static calculateTrajectory(position: Vector3, frame: number, speed: number, angle: number, direction: Vector3, target: TransformNode): void {
        const scope = Shell;
        const dta = Landscape.delta;
        const wind = Landscape.wind;
        const grv = Landscape.gravity;
        const time = frame * dta;
        const d = scope._tv1;

        Vector3.CrossToRef(Axis.Y, direction, d);
        Quaternion.RotationAxisToRef(d, -angle, scope._tq0);
        d.copyFrom(direction).applyRotationQuaternionInPlace(scope._tq0);
        d.scaleInPlace(speed * dta);
        const fall = grv * time;
        d.y -= fall * dta;
        target.position.copyFrom(d);
        scope._tv0.copyFrom(wind).scaleInPlace(dta);
        target.position.addInPlace(scope._tv0);
        target.position.addInPlace(position);

        target.lookAt(d);
    }

}


export { Shell };